package tp.pr4.cityLoader;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;

import tp.pr4.City;
import tp.pr4.Direction;
import tp.pr4.Place;
import tp.pr4.Street;
import tp.pr4.cityLoader.cityLoaderExceptions.WrongCityFormatException;
import tp.pr4.items.CodeCard;
import tp.pr4.items.Fuel;
import tp.pr4.items.Garbage;

/**
 * City loader from a txt file
 * @author Alvaro Saez Hernando
 * @author Noel Clemente Montero 
 */
public class CityLoaderFromTxtFile {
		
	private ArrayList<Place> _places;
	private ArrayList<Street> _streets;
	
	/**
	 * Default Constructor
	 */
	public CityLoaderFromTxtFile(){
		_places = new ArrayList<Place>();
		_streets = new ArrayList<Street>();
	}
	
	/**
	 * Returns the inital place of the city that has been loaded.
	 * @return The initial place.
	 */
	public Place getInitialPlace(){
		return _places.get(0);
	}
	
	/**
	 * Method that loads the cityMap from an Input Stream
	 * @param file The file stream to load from.
	 * @return The city map that has been loaded
	 * @throws java.io.IOException Exception if there are reading / writing errors.
	 * @throws WrongCityFormatException Exception that is thrown if the .txt file 
	 * isn't shaped to be a city map.
	 */
	public City loadCity(java.io.InputStream file)throws java.io.IOException, WrongCityFormatException{
		try{
			String fileData = fileToString(new InputStreamReader(file));
			City city = new City();
			StringTokenizer stringMap = new StringTokenizer(fileData);
			
			String data = stringMap.nextToken();
			testName(data, "BeginCity");
	
			createPlaces(stringMap);
			createStreets(stringMap);
			createItems(stringMap);
	
			data = stringMap.nextToken();		
			testName(data, "EndCity");
			Street[] test= new Street[_streets.size()];
			for (int i=0; i<_streets.size(); i++){
				test[i]=_streets.get(i);
			}
			city = new City(test);
			return city;
		}catch (NoSuchElementException e){
			throw new WrongCityFormatException ();}
		
		catch (NumberFormatException e){
			throw new WrongCityFormatException ();}
		
	}
	
	//-------------------------METODOS PRIVADOS-----------------------------------------------//
	
	/**
	 * Creates places that read from file
	 * @param token
	 * @throws WrongCityFormatException
	 * @throws IOException
	 */
	private void createPlaces(StringTokenizer token) throws WrongCityFormatException, IOException{
		String data = token.nextToken();
		testName(data, "BeginPlaces");
		data = token.nextToken();
		int nextPlace = 0;
		while (!data.equalsIgnoreCase("EndPlaces")){
			testName(data, "place");
			readNumber(token, nextPlace++);
			String name = token.nextToken();			
			String description = readDescription(token);
			data = token.nextToken();
			boolean space;
			if (data.equalsIgnoreCase("noSpaceShip")){
				space = false;
			}else if (data.equalsIgnoreCase("SpaceShip")){
				space = true;
			} else {
				throw new WrongCityFormatException();
			}
			Place place = new Place(name, space, description);
			_places.add(place);

			data = token.nextToken();
		}
		testName(data, "EndPlaces");
	}	

	/**
	 * Creates street that read from file
	 * @param token
	 * @throws WrongCityFormatException
	 * @throws IOException
	 */
	private void createStreets(StringTokenizer token) throws WrongCityFormatException, IOException{
		String data = token.nextToken();
		testName(data, "BeginStreets");
		data = token.nextToken();
		String password = "";
		int nextStreet = 0;
		while (!data.equalsIgnoreCase("EndStreets")){
			testName(data, "street");
			readNumber(token, nextStreet++);
			data = token.nextToken();
			testName(data, "place");
			int numSourcePlace = readNumber(token);
			data = token.nextToken();
			Direction direction = parseDirection(data.toUpperCase());
			data = token.nextToken();
			testName(data, "place");
			int numTargetPlace = readNumber(token);
			data = token.nextToken();
			boolean isOpen = false;
			if (data.equalsIgnoreCase("open")){
				isOpen = true;
			}else if (data.equalsIgnoreCase("closed")){
				isOpen = false;
			} else {
				throw new WrongCityFormatException();
			}
			if (token.hasMoreTokens() && isOpen == false){
				password = token.nextToken();
			}
			Place placeSource = findPlace(_places, numSourcePlace); 
			Place placeTarget = findPlace(_places, numTargetPlace);
			Street street = new Street(placeSource, direction, placeTarget, isOpen, password);
			_streets.add(street);

			data = token.nextToken();
		}
		testName(data, "EndStreets");
	}
	
	/**
	 * Creates item that read from file
	 * @param token
	 * @throws WrongCityFormatException
	 * @throws IOException
	 */
	private void createItems(StringTokenizer token) throws WrongCityFormatException, IOException{
		String data = token.nextToken();
		testName(data, "BeginItems");
		data = token.nextToken();
		int nextItem = 0;
		while (!data.equalsIgnoreCase("EndItems")){
			if (data.equalsIgnoreCase("fuel")){
				addFuel(token, nextItem++);
			} else if (data.equalsIgnoreCase("codecard")){
				addCodeCard(token, nextItem++);
			} else if (data.equalsIgnoreCase("garbage")){
				addGarbage(token, nextItem++);
			} else {
				throw new WrongCityFormatException();
			}
			data = token.nextToken();
		}
		testName(data, "EndItems");
	}
	
	/**
	 * Adds fuel to the site indicating the file
	 * @param token
	 * @param nextItem
	 * @throws WrongCityFormatException
	 */
	private void addFuel(StringTokenizer token, int nextItem) throws WrongCityFormatException {
		readNumber(token, nextItem);
		String name = token.nextToken();
		String description = readDescription(token);
		int power = readNumber(token);
		int times = readNumber(token);
		String data = token.nextToken();
		testName(data, "place");
		int numPlace = readNumber(token);
		Place place = findPlace(_places, numPlace);
		Fuel fuel = new Fuel(name, description, power, times);
		place.addItem(fuel);
	}

	/**
	 * Adds codeCard to the site indicating the file
	 * @param token
	 * @param nextItem
	 * @throws WrongCityFormatException
	 */
	private void addCodeCard(StringTokenizer token, int nextItem) throws WrongCityFormatException {
		readNumber(token, nextItem);
		String name = token.nextToken();
		String description = readDescription(token);
		String password = token.nextToken();
		String data = token.nextToken();
		testName(data, "place");
		int numPlace = readNumber(token);
		Place place = findPlace(_places, numPlace);
		CodeCard card = new CodeCard(name, description, password);
		place.addItem(card);
	}
	
	/**
	 * Adds garbage to the site indicating the file
	 * @param token
	 * @param nextItem
	 * @throws WrongCityFormatException
	 */
	private void addGarbage(StringTokenizer token, int nextItem) throws WrongCityFormatException {
		readNumber(token, nextItem);
		String name = token.nextToken();
		String description = readDescription(token);
		int recycledMaterial = readNumber(token);
		String data = token.nextToken();
		testName(data, "place");
		int numPlace = readNumber(token);
		Place place = findPlace(_places, numPlace);
		Garbage garbage = new Garbage(name, description, recycledMaterial);
		place.addItem(garbage);
	}

	/**
	 * Parse the direction that send the file
	 * @param dir
	 * @return direction
	 */
	private Direction parseDirection(String dir){
		Direction direction;
		switch (dir){
			case "NORTH": 
				direction = Direction.NORTH;
				break;
			case "WEST": 
				direction = Direction.WEST;
				break;
			case "EAST": 
				direction = Direction.EAST;
				break;
			case "SOUTH": 
				direction = Direction.SOUTH;
				break;
			default: 
				direction = Direction.UNKNOWN;
				break;
		}
		return direction;
	}
	
	/**
	 * Passes the file to a string
	 * @param reader
	 * @return string
	 * @throws IOException
	 */
	private static String fileToString(InputStreamReader reader) throws IOException {
		// Lee el fichero completo en un String
		StringBuffer sb = new StringBuffer();
		BufferedReader in = new BufferedReader(reader);
		String s;
		while((s = in.readLine()) != null) {
			sb.append(s);
			sb.append("\n");
		}
		in.close();
		return sb.toString();
	}

	/**
	 * Checks two strings
	 * @param data
	 * @param name
	 * @throws WrongCityFormatException
	 */
	private void testName(String data, String name) throws WrongCityFormatException {
		if(!data.equalsIgnoreCase(name))
			throw new WrongCityFormatException();		
	}

	/**
	 * Convert a string to int and checks
	 * @param token
	 * @param expectedNumber
	 * @return int
	 * @throws WrongCityFormatException
	 */
	private int readNumber(StringTokenizer token, int expectedNumber) throws WrongCityFormatException {
		String data = token.nextToken();
		int number = Integer.parseInt(data);
		if(number != expectedNumber)
			throw new WrongCityFormatException();		
		return number;
	}

	/**
	 * Convert a string to int
	 * @param token
	 * @return int
	 * @throws WrongCityFormatException
	 */
	private int readNumber(StringTokenizer token) throws WrongCityFormatException {
		String data = token.nextToken();
		int number = Integer.parseInt(data);
		return number;
	}

	/**
	 * Method that find a place by index. The values must be consecutives and 
	 * the method checks that index is not out of bounds.
	 * @param places
	 * @param index
	 * @return
	 * @throws WrongCityFormatException
	 */
	private Place findPlace(ArrayList<Place> places, int index) throws WrongCityFormatException {
		if(index >= places.size())
			throw new WrongCityFormatException();
		Place p = places.get(index);
		return p;
	}
	
	/**
	 * lee la descripcion con ""
	 * @param token
	 * @return description
	 */
	private String readDescription(StringTokenizer token) {
		String description = token.nextToken();
		if (description.startsWith("\"")) {
			do {
				description += token.nextToken();
			} while(description.endsWith("\""));
		}
		else {
			description = description.replace('_' ,  ' ');
		}		
		return description;
	}
}
